# -*-perl-*-
# $Id$
# Copyright (C) 2002-2005 Freescale Semiconductor, Inc.
# Distributed under terms of the Perl license, which is the disjunction of
# the GNU General Public License (GPL) and the Artistic License.

=pod
=begin reST
=begin Description
This writer creates an output in LaTeX format.  It is definitely in alpha
test state and will be reworked when the final docutils LaTeX writer
becomes available.

It uses the following output defines:

-W author-skip=<text>
                         The amount to skip after the author and before
                         address/organization (default is "12pt").
-W caption=<after|before>
                         Specify that a figure caption should appear
                         after or before the figure (default is "after").
-W chapter[=0|1]         Specify that the top-level sections should be
                         \chapter (default is 0 if documentclass is
                         "article" and 1 otherwise).
-W cloak-email-addresses[=<0|1>]
                         Enables cloaking of email addresses to keep
                         spambots from harvesting email addresses.
                         Default is 0.
-W documentclass=<text>  Specify documentclass for the output
                         (default is "article").
-W documentclass-opts=<text>
                         Specify the options for the documentclass
                         command (default is "").
-W footer[=1]            If 1, specify that the footer decoration 
                         generated by the RST transform should be
                         included in the document (default is 0).
-W footnote-links[=1]    If 1, specify that link URIs should be placed
                         into footnotes (default is 0).
-W image-ext=<text>      The file type to which to coerce figures (
                         default is "eps").
-W index[=1]             If 1, specify that an index should be created
                         from inline targets and indirect references
                         to them (default is 0).
-W inputs=<list>         Specify comma-separated list of files to \\input
-W max-unwrapped-colsize
                         The maximum length of a string in a
                         column of a table without forcing the
                         width of the column and turning on
                         wrapping for the entry.  Setting it to 0 
                         ensures that all tables will be exactly
                         the width of the text, even if the table's
                         natural width may be smaller. Setting it
                         too large may result in tables that
                         overflow the column boundary (default is 8).
-W omit-docinfo=<regexp>
                         Omit any docinfo section(s) that match the
                         regular expression.
-W packages=<list>       Specify comma-separated list of packages to \\use
-W sidebar=<margin|float>
                         Whether a sidebar should be processed as
                         a paragraph in the margin or as a
                         floating box within the text.
                         Processing as a margin paragraph
                         requires that \\marginparwidth have a
                         reasonable value and may require a raw
                         directive with a \\vspace -<dist> at the
                         top to keep it from running off the page
                         (default is "float").

There are a number of commands defined which specify default
styles for rendering various items.  These default styles can be
overridden by putting \\renewcommand definitions for them into
some file.tex and then invoking with ``-W inputs=file``.  These
commands are

\\styleadmonitiontitle
   Argument: the title (type) of the admonition.  Default is
   centered boldface followed by a colon.
\\styleaddress
   Argument: an organization, address, or contact.  Default is normal.
\\styleauthor
   Argument: an author.  Default is emphasized.
\\styleclassZZZ
   Argument: the contents of a compound directive with class ZZZ.
\\stylefieldname
   Argument: the name of a field.  Default is boldface followed
   by a colon.
\\stylelegendtitle
   Argument: the word "Legend".  Default is boldface followed
   by a colon.
\\styleliteralblock
   Argument: none, but encloses a verbatim section.  Default is null.
\\styleoption
   Arguments: the option string, the option argument(s) (may be
   null). Default is teletype option string followed by
   non-breaking space and italic option argument(s).
\\stylesidebartitles
   Arguments: the title for a sidebar, the subtitle for a sidebar
   (may be null).  Default is centered boldface title followed by
   centered italic subtitle.  Is not invoked if sidebar's title
   is null.
\\styleterm
   Arguments: description term, description classifier (may be null).
   Default is italic term followed by italic ": classifier" if
   the classifier is not null.
\\styletitle
   Arguments: the document's title, the document's subtitle (may
   be null).  Default is the title followed by ":", a newline and
   the subtitle in a smaller font, if the subtitle is not null.
\\styletopictitle
   Arguments: the title of a topic.  Default is bold face.

It is probably easiest to define these by first creating a .tex
file with the writer and then copying the definition you want to
modify from the top of the generated file into your style file,
changing the "\\newcommand" to "\\renewcommand" and supplanting
the definition.
=end Description
=end reST
=cut

sub BEGIN {
    # My -W flags
    use vars qw($author_skip $caption $chapter $cloak_email_addresses
		$documentclass $documentclass_opts $footer
		$footnote_links $image_ext $index $inputs
		$max_unwrapped_colsize $omit_docinfo $packages $sidebar);

    # Static globals
    use vars qw(%ENUMS @ROMAN @ROMANS %FOOTNOTE_SYMBOLS);

    # Run-time globals
    use vars qw(@SECTION_LEVELS @AUTHORS $DATE $DOCINFO $PREAMBLE
		$SUBTITLE @LITBOXES @CITATIONS %SYSTEM_MESSAGE
		%PACKAGES $BEGIN_DONE %COMPOUND_CLASSES %INLINE_CLASSES);

    # Defaults for -W arguments
    $author_skip 	   = '12pt'    unless defined $author_skip;
    $author_skip           = $$author_skip if ref $author_skip eq 'SCALAR';
    $documentclass         = 'article' unless defined $documentclass;
    $documentclass_opts    = ''        unless defined $documentclass_opts;
    $max_unwrapped_colsize = 8         unless defined $max_unwrapped_colsize;
    $caption               = 'after'   unless defined $caption;
    $cloak_email_addresses = ''        unless defined $cloak_email_addresses;
    $sidebar               = 'float'   unless defined $sidebar;
    $footer                = 0         unless defined $footer;
    $footnote_links        = 0         unless defined $footnote_links;
    $index                 = 0         unless defined $index;
    $inputs                = ''        unless defined $inputs;
    $image_ext             = "eps"     unless defined $image_ext;

    my @packages = split /,/, $packages if defined $packages;
    $PACKAGES{$_} = 1 foreach @packages;
    my @footnote_symbols = @Text::Restructured::docutils::transforms::references::FOOTNOTE_SYMBOLS;
    @FOOTNOTE_SYMBOLS{@footnote_symbols} = (1 .. @footnote_symbols);
}

# Converts a symbol-style footnote to its numeric equivalent
# Inputs: label
# Outputs: number or undef
sub FootnoteSymbol {
    my ($label) = @_;

    $label =~ s/\\\#/\#/g;
    my $sym = $FOOTNOTE_SYMBOLS{substr($label, 0, 1)};
    return $sym ? ((keys %FOOTNOTE_SYMBOLS) * (length($label)-1) + $sym) :
	undef;    
}

# Makes a string safe by quoting special characters
sub ProtectString {
    my ($string, $parsed_lit) = @_;
    $_ = $string;

    # Do some substitutions
    # Do this one first...
    s/([\\{}])/$1 eq "\\" ? '\ensuremath{\backslash}' : "\\$1"/ge;

    s/([\$&%\#_~])/\\$1/g;
    s/(\^)/\\textasciicircum /g;
    s/([<>|])/\\ensuremath{$1}/g;
    if (! $parsed_lit) {
	s/ <= / \\ensuremath{\\leq} /g;
	s/ >= / \\ensuremath{\\geq} /g;
	s/ == / \\ensuremath{=} /g;
	s/ != / \\ensuremath{\\neq} /g;
	s/(\A|\s)(-\d+)/$1\\ensuremath{$2}/g;
	s/(\s)\'/$1\`/gm;
	s/(?![\(\[\{])(\S)\"/$1\'\'/g;  # Needs matching } to parse schema
	s/\"(\S)/\`\`$1/g;
    }
    return $_;
}

# Checks to see if the DOM has any associated classes with it, and if so,
# generates a styling command around the string
sub StyleClasses {
    my ($dom, $str) = @_;
    my $class = $dom->{attr}{classes} ? $dom->{attr}{classes}[0] : '';
    $class =~ tr/a-zA-Z0-9//cd;
    if ($class) {
	$INLINE_CLASSES{$class} = 1;
	return "\\styleinline$class\{$str}";
    }
    return $str;
}

sub Warn {
    print STDERR "latex writer: Warning: $_[0]\n";
}

# This phase marks elements as being contained within footnotes or literals
# and computes nesting levels for sections and enumerated_lists.
phase MARK = {
    sub document = { # MARK
	my ($dom, $str) = @_;
	my %info = ('bullet'=>0, 'enum'=>0, 'foot'=>0, 'nest'=>0, 'lit'=>0);
	$dom->Recurse
	    (
	     sub {
		 my ($dom, $when, $info) = @_;
		 my $tag = $dom->tag;
		 if ($tag eq 'section') {
		     if ($when eq 'pre') {
			 $dom->{nest} = $info->{nest};
			 $info->{nest}++;
		     }
		     else { $info->{nest}--; }
		 }
		 elsif ($tag eq 'enumerated_list') {
		     if ($when eq 'pre') {
			 $info->{enum}++;
			 $dom->{nest} = $info->{enum};
		     }
		     else { $info->{enum}--; }
		 }
		 elsif ($tag eq 'bullet_list') {
		     if ($when eq 'pre') {
			 $info->{bullet}++;
			 $dom->{nest} = $info->{bullet};
		     }
		     else { $info->{bullet}--; }
		 }
		 elsif ($tag eq 'footnote') {
		     $info->{foot} += $when eq 'pre' ? 1 : -1;
		 }
		 elsif ($tag eq 'footnote_reference') {
		     $dom->{infootnote} = $info->{foot};
		 }
		 elsif ($tag =~ /^(literal|doctest)/) {
		     $info->{lit} += $when eq 'pre' ? 1 : -1;
		 }
		 elsif ($tag eq 'parsed_literal') {
		     $info->{parsedlit} += $when eq 'pre' ? 1 : -1;
		 }
		 elsif ($tag eq '#PCDATA') {
		     ($dom->{inliteral}) = 
			 (grep($info->{$_}, qw(lit parsedlit)), 0);
		 }
		 return 0;
	     }
	     , 'both', \%info);
	return;
    }
}

phase PREPROCESS = {
    sub \#PCDATA = { # PREPROCESS
	my ($dom, $str) = @_;

	my $parent = $dom->parent();
	$_ = $dom->{text};
	if (! $dom->{inliteral} &&
	    $parent->tag !~ /raw|option_string/) {
	    my $indx = $parent->index($dom);
	    s/^\"/\'\'/ if $indx > 0;
	    s/\"$/\`\`/ if $indx < $parent->num_contents - 1;
	    $_ = ProtectString($_);
	}
	elsif ($dom->{inliteral} eq 'parsedlit') {
	    $_ = ProtectString($_, 'parsedlit');
	}
	return $_;
    }

#     sub section|topic = { # PREPROCESS
# 	my ($dom, $str) = @_;

# 	# Remove title from contents
# 	if ($dom->first->tag eq 'title') {
# 	    my $titledom = shift $dom->contents;
# 	    $dom->{title} = $titledom->{val};
# 	    chomp $dom->{title};
# 	}
#     }

    sub title|subtitle|caption|label = { # PREPROCESS
	my ($dom, $str) = @_;
	return $str;
    }

    sub figure = { # PREPROCESS
	my ($dom, $str) = @_;
	my $d;
	while ($d = shift @{$dom->{content}}) {
	    # uncoverable branch false count:3 note:Defensive programming
	    if ($d->tag eq 'image') {
		$dom->{image} = $d->{image};
	    }
	    elsif ($d->tag eq 'caption') {
		$dom->{caption} = $d;
	    }
	    elsif ($d->tag eq 'legend') {
		$dom->{legend} = $d;
	    }
	}
	@{$dom->{content}} =
	    grep(defined $_,(map($dom->{$_}, qw(caption legend))));
	return;
    }

    sub image = { # PREPROCESS
	my ($dom, $str) = @_;

	# If the type of the image is not $image_ext, see if we can by
	# any chance convert it.
	my $uri = $dom->{attr}{uri};
	$uri =~ m!([^/]+\.)[^/]+$!;
	my $local_uri = "$1$image_ext";
	if ($uri !~ /\.$image_ext$/ && $uri ne $local_uri) {
	    `convert $uri $local_uri > /dev/null 2>&1`
		if -r $uri && ! -r $local_uri;
	    $uri = $local_uri if -r $local_uri;
	}
	my %attr = (map(($_,"$dom->{attr}{$_}" .
			 ($dom->{attr}{$_} =~ /[a-z]/ ? '' : 'pt')),
			grep(defined $dom->{attr}{$_},
			     qw(height width))),
		    map(($_,$dom->{attr}{$_}/100),
			grep(defined $dom->{attr}{$_},
			     qw(scale)))
		    );
	my $attr = %attr ?
	    "[" . join(',',map("$_=$attr{$_}",keys %attr)) . "]" : '';
	my $image = $uri =~ /\.$image_ext$/ ?
	    do { $PACKAGES{graphicx} = 1; "\\includegraphics$attr\{$uri}" }
	    : "{\\tt $uri}";
	$dom->{image} = $image;
	return;
    }
    
    sub tgroup = { # PREPROCESS
	my ($dom, $str) = @_;
	my @newcontents;
	foreach (@{$dom->{content}}) {
	    if ($_->tag eq 'colspec') {
		my $colwidth = $_->{attr}{colwidth};
		$dom->{totwidth} += $colwidth;
		push @{$dom->{colwidths}}, $colwidth;
	    }
	    else {
		push @newcontents, $_;
	    }
	}
	@{$dom->{content}} = @newcontents;
    }

    sub thead|tbody = { # PREPROCESS
 	my ($dom, $str) = @_;

	my $parent = $dom->parent();
	# Create an array with one entry per column for keeping track of
	# entries that are covered from previous rows.
	$dom->{borrows} = [(0) x $parent->{attr}{cols}];
	$dom->{borrowcols} = [(0) x $parent->{attr}{cols}];
    }

    sub footnote = { # PREPROCESS
	my ($dom, $str) = @_;
	my $label = shift @{$dom->{content}};
	$dom->{label} = $label->{val};
	return;
    }

    sub footnote_reference = { # PREPROCESS
 	my ($dom, $str) = @_;
	$dom->{label} = $str;
	@{$dom->{content}} = ();
	return if $dom->{infootnote};
	my $footnote = $dom->{resolved};
	if (defined $footnote && ! $footnote->{'.latexref'}) {
	    my @content = @{$footnote->{content}};
	    # uncoverable branch false note:Assert has label
	    shift @content if $content[0]->tag eq 'label';
	    @{$dom->{content}} = @content;
	    $footnote->{'.latexref'} = 1;
	}
	# May need to trim trailing spaces from predecessor
	my $parent = $dom->parent();
	my $indx = $parent->index($dom);
	if ($indx > 0 && $parent->child($indx-1)->tag eq '#PCDATA') {
	    $parent->child($indx-1)->{val} =~ s/ *$//;
	}
	return;
    }

    sub citation = { # PREPROCESS
	my ($dom, $str) = @_;

	push @CITATIONS, $dom;
	# Remove label from contents
	# uncoverable branch false note:Assert has label
	if ($dom->first->tag eq 'label') {
	    my $labeldom = shift @{$dom->{content}};
	    $dom->{label} = $labeldom->{val};
	    chomp $dom->{label};
	}
	return;
    }

    sub sidebar = { # PREPROCESS
	my ($dom, $str) = @_;

	$dom->{title} = shift @{$dom->{content}}
	    if $dom->first->tag eq 'title';
	$dom->{subtitle} = shift @{$dom->{content}}
	    if $dom->first->tag eq 'subtitle';
	return;
    }

    sub system_message = { # PREPROCESS
	my ($dom, $str) = @_;
	$SYSTEM_MESSAGE{$dom->{attr}{ids}[0]} = $dom
	    if defined $dom->{attr}{ids}[0];
	return "$dom->{attr}{type}: $str";
    }

    # For system_message
    sub literal_block = { # PREPROCESS
	my ($dom, $str) = @_;
	return "\\begin{verbatim}\n$str\\end{verbatim}\n";
    }

    # For system_message
    sub paragraph = { # PREPROCESS
	my ($dom, $str) = @_;
	return "$str";
    }

    sub raw|inline = { # PREPROCESS
	my ($dom, $str) = @_;
	return unless $dom->{attr}{format} &&
	    $dom->{attr}{format} =~ /\blatex\b/;
	chomp $str;
	return $str;
    }

    sub document = { # PREPROCESS
	my ($dom, $str) = @_;

	@SECTION_LEVELS = 
	    qw(section subsection subsubsection paragraph subparagraph);
	unshift @SECTION_LEVELS, 'chapter'
	    unless ($documentclass eq 'article' ||
		    defined $chapter && $chapter == 0) ;

	# Put the document's title/subtitle under the document's docinfo
	# (create the docinfo if it does not exist)
	my $docinfo;
	foreach (@{$dom->{content}}) {
	    if ($_->tag eq 'docinfo') {
		$docinfo = $_;
		last;
	    }
	}
	my $created_docinfo = ! $docinfo;
	$docinfo ||= Text::Restructured::DOM->new('docinfo');
	if ($dom->first->tag eq 'title') {
	    $docinfo->splice(0, 0, shift @{$dom->{content}});
	    $dom->{_latex}{has_title} = 1;
	}
	# Grab the subtitle if it exists
	$docinfo->splice(1, 0, shift @{$dom->{content}})
	    if $dom->first->tag eq 'subtitle';
	$dom->splice(0, 0, $docinfo) if $created_docinfo;

	return;
    }
}

phase PROCESS = {
    sub \#PCDATA = { # PROCESS
	my ($dom, $str) = @_;
	return $dom->{val};
    }
    
    # Return the generated section number, without the garbage 
    # characters generated at the end.
    sub generated = { # PROCESS
	return;
    }

    # Store the section title in the dom.
    sub section = { # PROCESS
 	my ($dom, $str) = @_;
	return if defined $dom->{attr}{classes} &&
	    $dom->{attr}{classes}[0] eq 'system-messages';
	my $nest = $dom->{nest};
	my $section = $SECTION_LEVELS[$nest > $#SECTION_LEVELS ?
				      $#SECTION_LEVELS : $nest];
	# Remove title from contents
	# uncoverable branch false note:Assert title is present
	if ($dom->first->tag eq 'title') {
	    my $titledom = shift @{$dom->{content}};
	    $dom->{title} = $titledom->{val};
	    chomp $dom->{title};
	}
	$str = join('',map(defined $_->{val} ? $_->{val} : '',
			   @{$dom->{content}}));
 	return "\n\\$section\{$dom->{title}}\n$str";
    }

    sub author = { # PROCESS
	my ($dom, $str) = @_;
	chomp $str;
	push @AUTHORS, $str;
	return;
    }

    sub docinfo = { # PROCESS
	my ($dom, $str) = @_;
	my $child;
	my %docinfo;
	my @fields;
	foreach $child (@{$dom->{content}}) {
	    if ($child->tag ne 'field') {
		$docinfo{$child->tag} = $child->{val};
	    }
	    else {
		push @fields, $child->{val};
	    }
	}
	my $title = $docinfo{title} || '';
	chomp $title;
	my $st    = $docinfo{subtitle} || '';
	chomp $st;
	$DOCINFO .= "\\title{\\styletitle{$title}{$st}}\n" if $title ne '';
	my $authors = join(' \hfill ',map("\\styleauthor{$_}",@AUTHORS));
	my @address = grep(defined $_,
			   map($docinfo{$_},
			       qw(organization address contact)));
	my $address = sprintf "\\styleaddress{%s}", join("\\\\\n",@address)
	    if @address;
	my @info = grep(defined $_,
			map($docinfo{$_},
			    qw(copyright status revision version)));
	my $info = join("\\\\[0pt]\n",
#			map("\\parbox{\\textwidth}{\\center $_}",
			map("\\styledocinfo{$_}",
			    @info,@fields))
	    if @info+@fields;
	$author_skip .= 'pt' if $author_skip =~ /^\d+$/;
	my $skip = $author_skip ? "[$author_skip]" : '';
	my $author = join("\\\\$skip\n",
			  grep(defined $_, ($authors, $address, $info)));
	$DOCINFO .= "\\author{$author}\n" if $author;
	$DOCINFO .= "\\date{$DATE}\n" if $DATE;
	return ;
    }

    sub date = { # PROCESS
	my ($dom, $str) = @_;
	($DATE) = $dom->first->{text} =~ m!(\d+[-/]\d+[-/]\d+)!;
	return;
    }

    sub title|organization|contact = { # PROCESS
	my ($dom, $str) = @_;

	my $parent = $dom->parent();
	chomp $str;
	if ($parent->tag eq 'topic') {
	    $parent->{title} = $str;
	    return;
	}
	return $str;
    }

    sub copyright = { # PROCESS
	my ($dom, $str) = @_;
	chomp $str;
	$str =~ s/\(C\)(opyright)?/\\copyright/g;
	return $str;
    }

    sub status|revision|version = { # PROCESS
	my ($dom, $str) = @_;
	chomp $str;
	my $tag = $dom->tag;
	substr($tag,0,1) =~ tr/a-z/A-Z/;
	return "\\stylefieldname{$tag} $str";
    }

    sub address = { # PROCESS
	my ($dom, $str) = @_;
	my @lines = split(/\n/, $str);
	return join "\\\\\n", @lines;
    }

    sub field = { # PROCESS
	my ($dom, $str) = @_;

	my $parent = $dom->parent();
	my $val = $dom->child(1)->{val};
	chomp $val;
	$val =~ s/\n\n/\\\\\n/g if $parent->tag ne 'field_list';
	my $fieldname = $dom->first->{val};
	chomp $fieldname;
	$fieldname =~ s/^\n//;
	return $parent->tag eq 'field_list' ?
	    "\\item[\\stylefieldname{$fieldname}] $val\n" :
	    "\\stylefieldname{$fieldname} $val";
    }

    sub field_name = { # PROCESS
	my ($dom, $str) = @_;
	substr($str,0,1) =~ tr/a-z/A-Z/;
	return $str;
    }

    sub field_body = { # PROCESS
	my ($dom, $str) = @_;
	$str =~ s/^\n//;
	return $str;
    }

    sub substitution_definition|authors = { # PROCESS
	my ($dom, $str) = @_;
	return;
    }

    sub comment = { # PROCESS
	my ($dom, $str) = @_;
	$str =~ s/^/% /gm;
	$str .= "\n" unless $str =~ /\n$/;
	$str;
    }

    sub paragraph = { # PROCESS
	my ($dom, $str) = @_;
	$str .= "\n" unless $str =~ /\n$/;
	return "\n$str";
    }

    sub topic = { # PROCESS
	my ($dom, $str) = @_;
	$str =~ s/^\n//;
	my $class = $dom->{attr}{classes} ? $dom->{attr}{classes}[0] : '';
	if ($class eq 'abstract') {
	    my @topic = 
		("\\begin{abstract}\n",
		 $str,
		 "\\end{abstract}\n",
		 );
	    return join '', @topic;
	}
	elsif ($class eq 'dedication') {
	    my @topic = 
		("\\$SECTION_LEVELS[0]*{Dedication}\n",
		 $str,
		 );
	    return join '', @topic;
	}
	elsif ($class eq 'contents') {
	    return "\\tableofcontents\n";
	}

	Warn("unsupported type of topic: $class") if $class;
	return "\\styletopictitle{$dom->{title}} $str"
    }

    sub citation_reference { # PROCESS
	my ($dom, $str) = @_;
	return "\\cite{$str}";
    }

    sub list_item { # PROCESS
	my ($dom, $str) = @_;

	my $parent = $dom->parent();
	my $list_type = $parent->tag;
	chomp $str;
	return "\\item $str\n";
    }

    sub enumerated_list { # PROCESS
	my ($dom, $str) = @_;
	my @list;
	my $nest = $dom->{nest};
	my $roman = roman($nest);
	my $counter = "enum$roman";
	use vars qw(@ENUM_USED);
	# Since LaTeX does not support enumerated lists
	# nested more than 4 deep, create new counters for deeper ones.
	my $predef = $nest > 4 && ! $ENUM_USED[$nest]++ ?
	    "\\newcounter{$counter}" : '';
	my $start = defined $dom->{attr}{start} ?
	    "\\setcounter{$counter}{${\scalar($dom->{attr}{start}-1)}}" : '';
	my $enum = $ENUMS{$dom->{attr}{enumtype}};
	my ($pfx, $sfx) =
	    map(defined $dom->{attr}{$_} ? $dom->{attr}{$_} : '',
		qw(prefix suffix));
	@list =
	    ("$predef\\renewcommand{\\the$counter}{\\$enum\{$counter}}\\begin{list}{$pfx\\the$counter$sfx}{\\usecounter{$counter}}$start\n",
	     $str,
	     "\\end{list}\n");
	return join '', @list;

	BEGIN {
	    @ROMAN = ('', 'i', 'ii', 'iii', 'iv');
	    %ENUMS = ('arabic'=>'arabic', 'loweralpha'=>'alph',
		      'lowerroman'=>'roman', 'upperalpha'=>'Alph',
		      'upperroman'=>'Roman');
	    @ROMANS = (['', qw(i ii iii iv v vi vii viii ix)],
		       ['', qw(x xx xxx xl l lx lxx lxxx xc)],
		       ['', qw(c cc ccc cd d dc dcc dccc cm)],
		       ['', qw(m mm mmm)]);
	}

	# Given a number, returns a Roman numeral representing the number
	sub roman {
	    my ($num) = @_;

	    # uncoverable branch true note:Defensive programming
	    return 0 if $num < 1 or $num > 3999;
	    my @digits = reverse split(//,$num);
	    return join('',
			reverse map($ROMANS[$_][$digits[$_]], 0 .. $#digits));
	}
    }

    sub bullet_list { # PROCESS
	my ($dom, $str) = @_;
	my @list;
	if ($dom->{nest} > 4) {
	    @list = ("\\begin{list}{*}{}\n",
		     $str,
		     "\\end{list}\n",
		     );
	}
	else {
	    my $counter = "[\\labelitem$ROMAN[$dom->{nest}]]";
	    @list = ("\\begin{itemize}\n",
		     $str,
		     "\\end{itemize}\n");
	}
	return join '', @list;
    }

    sub definition_list_item { # PROCESS
	my ($dom, $str) = @_;
	my ($term, $classifier, $definition) = ('') x 3;
	foreach (@{$dom->{content}}) {
	    if ($_->tag eq 'term') {
		$term = $_->{val};
	    }
	    elsif ($_->tag eq 'classifier') {
		$classifier = $_->{val};
	    }
	    else {
		$definition = $_->{val};
		chomp $definition;
	    }
	}
	return "\\item [\\styleterm{$term}{$classifier}] \\mbox{} \\\\ $definition\n";
    }

    sub definition_list { # PROCESS
	my ($dom, $str) = @_;
	my @list = ("\\begin{description}\n",
		    $str,
		    "\\end{description}\n",
		    );
	return join '', @list;
    }

    sub field_list { # PROCESS
	my ($dom, $str) = @_;
	
	my @list = ("\\begin{description}\n",
		    $str,
		    "\\end{description}\n",
		    );
	return join '', @list;
    }

    sub option_list { # PROCESS
	my ($dom, $str) = @_;

	my @list = (
		    "\\begin{description}\n",
		    $str,
		    "\\end{description}\n\n",
		    );
	return join '', @list;
    }

    sub option_list_item { # PROCESS
	my ($dom, $str) = @_;
	my ($opt, $desc) = map($_->{val}, @{$dom->{content}});
	chomp $desc;
	return "\\item [$opt] \\mbox{} \\\\ $desc\n";
    }

    sub option_group { # PROCESS
	my ($dom, $str) = @_;
	$str = join(', ', map($_->{val}, @{$dom->{content}}))
	    if @{$dom->{content}} > 1;
	return $str;
#	return "\\styleoptstring{$str}";
    }

    sub option { # PROCESS
	my ($dom, $str) = @_;
	my @vals = map($_->{val},@{$dom->{content}});
	my $opt = ProtectString(shift @vals);
	my $args = join ' ', @vals;
	return "\\styleoption{$opt}{$args}";
    }
    
    sub option_argument { # PROCESS
	my ($dom, $str) = @_;
	return $str;
#	return "\\styleoptarg{$str}";
    }

    # These pups just return whatever their contents returned
    sub term|classifier|definition|option_string|description|entry|caption|legend|decoration { # PROCESS
	my ($dom, $str) = @_;
	return $str;
    }

    sub block_quote { # PROCESS
	my ($dom, $str) = @_;
	return <<"EOS"
\\begin{quote}
$str
\\end{quote}
EOS
    ;
    }

    sub attribution { # PROCESS
	my ($dom, $str) = @_;
	chomp $str;
	return <<"EOS"
\\begin{flushright}
---$str
\\end{flushright}
EOS
	;
    }

    sub subscript { # PROCESS
	my ($dom, $str) = @_;
	return StyleClasses($dom, "\\ensuremath{_{$str}}");
    }

    sub superscript { # PROCESS
	my ($dom, $str) = @_;
	return StyleClasses($dom, "\\ensuremath{^{$str}}");
    }

    sub reference = { # PROCESS
	my ($dom, $str) = @_;
	chomp $str;
	if ($cloak_email_addresses &&
	    ($dom->{attr}{refuri} || '') =~ /^mailto:/) {
	    $str =~ s/\./\\raisebox{-.2em}{\$\\cdot\$}/g;
	    $str =~ s/(\@)/\$$1\$/g;
	}
	my $uri = $footnote_links && defined $dom->{attr}{refuri} ?
	    "\\renewcommand{\\thefootnote}{\\alph{footnote}}\\footnote{$dom->{attr}{refuri}}\\renewcommand{\\thefootnote}{\\arabic{footnote}}" : '';
	return "$str$uri";
    }

    sub emphasis|title_reference = { # PROCESS
	my ($dom, $str) = @_;
	return StyleClasses($dom, "{\\em $str}");
    }

    sub strong = { # PROCESS
  	my ($dom, $str) = @_;
  	return StyleClasses($dom, "{\\bf $str}");
    }

    sub (?:doctest|literal)_block = { # PROCESS
	my ($dom, $str) = @_;
	return "{\\styleliteralblock\\begin{verbatim}\n$str\\end{verbatim}}\n";
    }

    sub literal = { # PROCESS
	my ($dom, $str) = @_;

	# The problem with \verb in LaTeX is that is cannot be used
	# any the argument to any command, such as footnotes, column
	# spans in tables, etc.  SO, we need to predefine every
	# literal string in its own box and then just use the box at
	# the point where it occurs.  We also need to handle each line
	# of the string separately, to preserve line breaks.
	my @str = split(/\n/, $str);
	my @uses;
	use vars qw($LAST_LITERAL %LITID);
	foreach (@str) {
	    my $id;
	    if (! $LITID{$_}) {
		$id = "\\lit" . roman(++$LAST_LITERAL);
		$LITID{$_} = $id;
		push @LITBOXES, "\\newsavebox{$id}\\begin{lrbox}{$id}\\verb\331$_\331\\end{lrbox}\n";
	    }
	    else { $id = $LITID{$_} }
	    push @uses, "\\usebox{$id}";
	}
	
	return join("\\\\\n",@uses);
    }

    sub target = { # PROCESS
 	my ($dom, $str) = @_;
	return unless $str ne '';
	return "\\index{$str}$str";
    }

    sub attention|caution|danger|error|hint|important|note|tip|warning { # PROCESS
 	my ($dom, $str) = @_;
	my $title = $dom->tag;
	substr($title,0,1) =~ tr/a-z/A-Z/;
	$str .= "\n" unless $str =~ /\n$/;
	my @list = ("\n\\noindent\\fbox{\\parbox{\\linewidth}{\n",
		    "\\styleadmonitiontitle{$title}\n",
		    $str,
		    ,"}}\n",
		    );
	return join '', @list;
    }

    sub image = { # PROCESS
 	my ($dom, $str) = @_;
	return $dom->{image};
    }

    sub figure = { # PROCESS
 	my ($dom, $str) = @_;
	my $image = $dom->{image} || '';
	my $class = $dom->{attr}{classes} ? $dom->{attr}{classes}[0] : '';
	my $env = $class || 'figure';
  	my @figure =
  	    ("\\begin{$env}\n",
  	     defined $dom->{caption} && $caption eq 'before' ?
	     "\\caption{$dom->{caption}{val}}\n" : '',
  	     "\\centerline{$image}\n",
	     $dom->{legend} ? "\\stylelegendtitle{Legend}\n$dom->{legend}{val}" : '',
  	     defined $dom->{caption} && $caption ne 'before' ?
	     "\\caption{$dom->{caption}{val}}\n" : '',
  	     "\\end{$env}\n",
  	     );
 	return join '', @figure;
    }

    sub table = { # PROCESS
 	my ($dom, $str) = @_;
	# Create a table/figure environment if there is a title
	#my %content;
	my @content = grep defined $_->{val}, $dom->contents;
	my $title = $content[0]->tag eq 'title' ? shift @content : '';
	my ($has_table) = grep $_->tag eq 'tgroup', @content;
	my $env = $has_table ? 'table' : 'figure';
	#$content{$_->tag} = $_ foreach $dom->contents;
	my (@out1, @out2);
	if ($title) {
	    @out1 = ("\\begin{$env}\n",
		     "\\center\n");
	    @out2 = ("\\end{$env}\n");
	    if ($caption eq 'before') {
		push @out1, "\\caption{$title->{val}}\n";
	    }
	    else {
		unshift @out2, "\\caption{$title->{val}}\n";
	    }
	}
	return join '', @out1, map($_->{val}, @content), @out2;
    }

    sub tgroup = { # PROCESS
 	my ($dom, $str) = @_;
	# Need to compute our column widths
	my $cols = $dom->{attr}{cols};
#	my @colspecs = map(sprintf("p{%.4g\\dimen1}|",$_/$dom->{totwidth}),
#			   @{$dom->{colwidths}});
#	$colspecs[0] = "|$colspecs[0]";
#	my $colspec = join '',@colspecs;
	my $colspec = "|" . ("l|" x $cols);
	my @tgroup = ("\\par\\noindent\\dimen1=\\columnwidth\n",
		      "\\advance\\dimen1 -${\scalar(2*$cols)}\\tabcolsep\n",
		      "\\begin{tabular}{$colspec}\n",
		      "\\hline\n",
		      join("\\hline\n",
			   map($_->{val}, @{$dom->{content}})),
		      "\\end{tabular}\n",
		      );
	return join '', @tgroup;
    }

    sub thead|tbody = { # PROCESS
 	my ($dom, $str) = @_;
	return join '', map("$_->{val}", @{$dom->{content}});
    }

    sub row = { # PROCESS
 	my ($dom, $str) = @_;

	my $parent = $dom->parent();
	my @content = @{$dom->{content}};
	my $borrows = $parent->{borrows} ;
	my $borrowcols = $parent->{borrowcols} ;
	# uncoverable branch false note:Parent produces borrows
	my $cols = defined $borrows ? @$borrows : -1;
	my $i;
	my @entries;
	for ($i=0; $i < $cols; $i++) {
	    if ($borrows->[$i]) {
		if ($borrowcols->[$i] > 1) {
		    push @entries, "\\multicolumn{$borrowcols->[$i]}{l|}{}";
		    $i += $borrowcols->[$i]-1;
		}
		else {
		    push @entries, '';
		}
	    }
	    else {
		my $entry = shift @content;
		my $attr = $entry->{attr};
		my $rows = 1 + ($attr->{morerows} || 0);
		my $cols = 1 + ($attr->{morecols} || 0);
		my $j;
		for ($j=0; $j < $cols; $j++) {
		    $borrows->[$i+$j] += $rows;
		}
		$borrowcols->[$i] = $rows if $cols > 1;
		if ($attr->{align} && $attr->{align} !~ /^l/) {
		    my $colspec = ($i ? '' : '|') .
			substr($attr->{align},0,1) . '|';
		    push @entries,
		    "\\multicolumn{$cols}{$colspec}{$entry->{val}}";
		}
		elsif ($cols > 1 || @{$entry->{content}} > 1 ||
		       length($entry->{val}) > $max_unwrapped_colsize+2) {
		    my $gramps = $parent->parent();
		    my $colwidths = $gramps->{colwidths};
		    my $totwidth = $gramps->{totwidth};
		    my $mywidth = 0;
		    for ($j=0; $j < $cols; $j++) {
			$mywidth += $colwidths->[$i+$j];
		    }
		    my $colspec =
			sprintf("%sp{%.4g\\dimen1}|", ($i == 0 ? '|' : ''),
				$mywidth/$totwidth);
		    push @entries,
		    "\\multicolumn{$cols}{$colspec}{$entry->{val}}";
		}
		else {
		    push @entries, $entry->{val};
		}
		$i += ($attr->{morecols} || 0);
	    }
	}
	# Decrement each of the borrows numbers
	@$borrows = map(--$_, @$borrows);
	# Compute the hline/cline based upon the borrows
	my $hline = "\\hline";
	if (grep ($_,@$borrows)) {
	    # Need a series of cline's
	    my @lines;
	    my $i;
	    for ($i=0; $i < @$borrows; $i++) {
		next if $borrows->[$i];
		if (@lines && $i == $lines[-1][1]) {
		    $lines[-1][1]++;
		}
		else {
		    push @lines, [$i+1, $i+1];
		}
	    }
	    $hline = join ' ', map("\\cline{$_->[0]-$_->[1]}", @lines);
	}
	return join(' & ', @entries) . " \\\\ $hline\n";
    }

    sub footnote_reference = { # PROCESS
 	my ($dom, $str) = @_;
	# uncoverable branch false note:Assert has label
	my $label = defined $dom->{label} ? $dom->{label}: '';
	my $sym = FootnoteSymbol($label);
	$label = $sym if $sym;
	my $footnote = $str ne '' ? "\\footnote[$label]{$str}" :
	    "\\footnotemark[$label]";
	return "\\renewcommand{\\thefootnote}{\\fnsymbol{footnote}}$footnote\\renewcommand{\\thefootnote}{\\arabic{footnote}}"
	    if $sym;
	return "$footnote";
    }

    sub footnote = { # PROCESS
 	my ($dom, $str) = @_;
	return if $dom->{'.latexref'};
	# uncoverable branch false note:Assert has label
	my $label = defined $dom->{label} ? $dom->{label} : '';
	my $sym = FootnoteSymbol($label);
	return "\\renewcommand{\\thefootnote}{\\fnsymbol{footnote}}\\footnotetext[$sym]{$str}\\renewcommand{\\thefootnote}{\\arabic{footnote}}"
	    if $sym;
	return "\\footnotetext[$label]{$str}";
    }

    sub citation = { # PROCESS
 	my ($dom, $str) = @_;
	my $n_citations = @CITATIONS;
	use vars qw($DONE_CITATIONS);
	if (++$DONE_CITATIONS == $n_citations) {
	    my @citations = 
		("\\begin{thebibliography}{$n_citations}\n",
		 map(do {my $bib = $_; "\\bibitem{$bib->{label}} " .
			     join('',map($_->{val},@{$bib->{content}}));},
		     @CITATIONS),
		 "\\end{thebibliography}\n",
		 );
	    return join '',@citations;
	}
	return;
    }

    sub transition = { # PROCESS
	return "\\vspace{1em}\\hrule\\vspace{1em}\n\\par\\noindent";
    }

    sub parsed_literal = { # PROCESS
	my ($dom, $str) = @_;
	$str =~ s/^[ \t]+$//gm;
	$str =~ s/(.)\n(?=(\n))?/
	    "$1\\\\" . ($2 ? "[\\baselineskip]" : '') . "\n"/eg;
	$str =~ s/^(?!\\end)(.)\n(?!\n)/$1\\\\\n/gm;
	$str =~ s/ /~/g;
	$str =~ s/(\\[a-z]\w+)~/$1 /gi;
	my @verse = ("\\begin{verse}{\\tt\\styleliteralblock\n",
			  $str,
			  "}\\end{verse}\n");
	return join '', @verse;
    }

    sub line_block = { # PROCESS
	my ($dom, $str) = @_;
	my @verse = ("\\begin{verse}\n",
			  $str,
			  "\\end{verse}\n\n");
	return join '', @verse;
    }

    sub line { # PROCESS
	my ($dom, $str) = @_;
	if ($str eq '') {
	    $str = "\\vspace{\\baselineskip}";
	}
	else {
	    chomp $str;
	    $str =~ s/ /~/g;
	    $str =~ s/(\\\w+)~/$1 /g;
	    $str = "$str\\\\";
	}
	return "$str\n";
    }

    sub sidebar = { # PROCESS
	my ($dom, $str) = @_;
	my ($sb_start, $sb_end) = $sidebar eq 'margin' ? 
	    ('\marginpar{\fbox{\parbox[t]{\marginparwidth}{', '}}}') :
	    ('\begin{figure}\begin{center}\fbox{\fbox{\parbox{.8\columnwidth}{',
	     '}}}\end{center}\end{figure}');
	my ($title, $subtitle) = map $dom->{$_}{val} || '', qw(title subtitle);
	my @sidebar = ("$sb_start\n",
		       $title ne '' ?
			"\\stylesidebartitles{$title}{$subtitle\\fi}\n" : '',
		       $str,
		       "$sb_end\n",
		       );
	return join '', @sidebar;
    }

    sub inline = { # PROCESS
	my ($dom, $str) = @_;
	return StyleClasses($dom, $str);
    }

    sub compound = { # PROCESS
	my ($dom, $str) = @_;
	my $class = $dom->{attr}{classes} ? $dom->{attr}{classes}[0] : '';
	if ($class) {
	    $COMPOUND_CLASSES{$class} = 1;
	    return "\\styleclass$class\{$str}\n";
	}
	return $str;
    }

    sub problematic = { # PROCESS
	my ($dom, $str) = @_;
	my @str = split(/\n/, $str);
	my $sm = $SYSTEM_MESSAGE{$dom->{attr}{refid}};
	my $message = $sm->{val} || '';
	my $fn = "\\renewcommand{\\thefootnote}{\\alph{footnote}}\\footnote{$message}\\renewcommand{\\thefootnote}{\\arabic{footnote}}";
	
	return join("\n",map("{\\tt $_}$fn", @str));
    }

    sub system_message = { # PROCESS
	my ($dom, $str) = @_;
	return;
    }

    sub footer = { # PROCESS
	my ($dom, $str) = @_;
	return $footer ?
	    "\\vspace{1em}\\hrule\\vspace{1em}\n\\par\\noindent{\\small $str}\n"
	    : '';
    }

    sub raw = { # PROCESS
	my ($dom, $str) = @_;
	return unless $dom->{attr}{format} =~ /\blatex\b/;
	chomp $str;
	my $uri = $footnote_links && defined $dom->{attr}{refuri} ?
	    "\\renewcommand{\\thefootnote}{\\alph{footnote}}\\footnote{$dom->{attr}{refuri}}\\renewcommand{\\thefootnote}{\\arabic{footnote}}" : '';
	if ($dom->{attr}{head}) {
	    $PREAMBLE .= "$str$uri\n";
	    return;
	}
	return "$str$uri\n";
    }

    sub document = { # PROCESS
	my ($dom, $str) = @_;

	my @inputs = split(',',$inputs);
	my $ind_file = $dom->{TOP_FILE};
	$ind_file =~ s|\.[^/]+$|.idx|;
	my $decoration;
	# uncoverable branch false note:Assert PREPROCESS creates docinfo
	shift @{$dom->{content}} if $dom->first->tag eq 'docinfo';
	if ($dom->first->tag eq 'decoration') {
	    $decoration = $dom->first->{val};
	    shift @{$dom->{content}};
	    $str = join('', map($_->{val} || '', @{$dom->{content}}));
	}
	my @doc = ("\\documentclass" . ($documentclass_opts ne '' ?
					"[$documentclass_opts]" : '') .
		   "{$documentclass}\n",
		   "\\usepackage{latexsym}\n",
		   "% Style definitions that can be overridden in a user\n",
		   "% input file\n",
		   "\\def\\ifEq#1#2{\\def\\testa{#1}\\def\\testb{#2}\\ifx\\testa\\testb}\n",
		   "\\newcommand{\\styleaddress}[1]{#1}\n",
		   "\\newcommand{\\styleadmonitiontitle}[1]{\\centerline{\\bf #1:}}\n",
		   "\\newcommand{\\styleauthor}[1]{{\\em #1}}\n",
		   map("\\newcommand{\\styleclass$_}[1]{#1}\n",
		       sort keys %COMPOUND_CLASSES),
		   map("\\newcommand{\\styleinline$_}[1]{#1}\n",
		       sort keys %INLINE_CLASSES),
		   "\\newcommand{\\styledocinfo}[1]{\\setbox0\\hbox{\\parbox{.7\\textwidth}{\\global\\dimen255=\\baselineskip #1}}\\ifdim \\ht0 < \\dimen255 \\centerline{\\hbox{#1}}\\else\\copy0 \\fi }\n",
#		   "\\newcommand{\\styledocinfo}[1]{\\parbox{\\textwidth}{\\center #1}}\n",
		   "\\newcommand{\\stylefieldname}[1]{{\\bf #1:}}\n",
		   "\\newcommand{\\stylelegendtitle}[1]{{\\bf #1:}}\n",
		   "\\newcommand{\\styleliteralblock}[0]{}\n",
		   "\\newcommand{\\styleoption}[2]{{\\tt #1}\\ifEq{#2}{}\\else ~{\\it #2}\\fi}\n",
		   "\\newcommand{\\stylesidebartitles}[2]{\\begin{center}{\\bf #1}\\end{center}\\ifEq{#2}{}\\else\\begin{center}{\\it #2}\\end{center}}\n",
		   "\\newcommand{\\styleterm}[2]{{\\it #1\\ifEq{#2}{}\\else: #2\\fi}}\n",
		   "\\newcommand{\\styletitle}[2]{#1\\ifEq{#2}{}\\else:\\\\ {\\Large #2}\\fi}\n",
		   "\\newcommand{\\styletopictitle}[1]{{\\bf #1}}\n",
		   map("\\input{$_}\n",@inputs),
		   map("\\usepackage{$_}\n", sort keys %PACKAGES),
		   $DOCINFO,
		   $PREAMBLE,
		   "\\frenchspacing\n",
		   $index ? "\\makeindex\n" : '',
		   "\\begin{document}\n",
		   $dom->{_latex}{has_title} ? "\\maketitle\n" : '',
		   join('',@LITBOXES),
		   $str,
		   $decoration,
		   $index ? "\\input{$ind_file}\n" : '',
		   "\\end{document}\n",
		   );
	return join '', grep defined $_, @doc;
    }

    sub mathml = { # PROCESS
	my ($dom, $str) = @_;
	@PACKAGES{qw(dsfont amssymb)} = 1;
	my $val = $dom->{attr}{mathml} ? $dom->{attr}{mathml}->latex : $str;
	if ($dom->{attr}{mathml}) {
	    my $label = $dom->{attr}{mathml}{attr}{label} ?
		"\\label{$dom->{attr}{mathml}{attr}{label}}"
		: '';
	    my $mstyle = $dom->{attr}{mathml}->child(0);
	    if ((my $env = $mstyle->{attr}{env}) || $label) {
		$env ||= 'equation';
		# Use a special environment
		$val =~ s/^\$\$(.*)\$\$$/\\begin{$env}$label\n$1\n\\end{$env}\n/;
	    }
	}
	return $val;
    }

    sub .* = { # PROCESS
	my ($dom, $str) = @_;
	use vars qw(%PRINTED);
	Warn("no handler for tag ${\$dom->tag}")
	    unless defined $dom->{val} || $PRINTED{$dom->tag}++;
	return $str;
    }
}
